﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

namespace FloodFill {

    public class FloodFiller {

        //internal LinkedList<Vector3> list_sandy = new LinkedList<Vector3>();
        //internal LinkedList<Vector3> list_grassy = new LinkedList<Vector3>();

        //contiene tutti i nodi dello shape
        public List<Node> ListVisited { get; set; }

        //Lista usata per avere tutti i punti che cambiano da target -> !target
        public LinkedList<Node> ListEntryPoint { get; set; }

        //Lista per l'esplorazione del grafo
        public LinkedList<Node> ListOpen { get; set; }

        //Matrice per ottenere i punti esplorati
        public Node[,] MatrixEntryPoint { get; set; }

        private int cont_visited = 0;
        private int sizeX;
        private int sizeZ;
        private int target;         //intero da cercare nella biomeMap
        private int[,] biomeMap;
        
        /// <summary>
        ///     Per il processo di floodfill è necessaria:
        ///         -la matrice con interi
        ///         -la posizione iniziale da cui partire (imposta il target)
        /// </summary>
        /// <param name="biomeMap"></param>
        /// <param name="startX"></param>
        /// <param name="startZ"></param>

        public FloodFiller(int[,] biomeMap , int startX, int startZ)
        {
            this.sizeX = biomeMap.GetLength(0);
            this.sizeZ = biomeMap.GetLength(1);
            this.ListOpen = new LinkedList<Node>();
            this.ListEntryPoint = new LinkedList<Node>();
            this.MatrixEntryPoint = new Node[sizeX, sizeZ];
            this.ListVisited = new List<Node>();

            this.biomeMap = biomeMap;
            this.target = biomeMap[startX, startZ];
            FloodFillAlgorithm( SetValue ( new Node(startX, startZ) ) );
        }

        /// <summary>
        /// Algoritmo di floodfill
        /// </summary>
        /// <param name="startNode"></param>
        private void FloodFillAlgorithm(Node startNode )
        {
            cont_visited = 0;
            ListOpen.AddLast(startNode);

            while(ListOpen.Count > 0 )
            {
                Node n = ListOpen.Last.Value;
                ListOpen.RemoveLast();

                cont_visited++;

                if (n.Value == -1)  continue;
            
                //se è gia stato visitato esce
                if (IsChecked(n))   continue;
    
                //imposto come visitato il nodo
                //lo salvo come nodo visitato
                SetChecked(n);

                if (n.Value != target) {
                    //aggiungo il nuovo entry point e passo al nuovo nodo
                    AddEntryPoint(n);
                } else {
                    //esploro i nodi nelle 4 direzioni
                    ListOpen.AddLast( SetValue(n.Left() ));
                    ListOpen.AddLast( SetValue(n.Bottom()) );
                    ListOpen.AddLast( SetValue(n.Right()) );
                    ListOpen.AddLast( SetValue(n.Top()) );
                }

                //funzione di DEBUG che evita cicli brutti e infiniti con SA
                if (ListOpen.Count > 512 * 512 * 3)
                {
                    Debug.LogError("something is wrong...");
                    return;
                }

            }
        }

        private void AddEntryPoint(Node n)
        {
            if ( n.ListEntryPointParents.Count == 0 )
                ListEntryPoint.AddLast(MatrixEntryPoint[n.x, n.z]);
            MatrixEntryPoint[n.x, n.z].AddEntryPoint(n.Parent);
        }

        internal bool IsOutOfBounds(int x , int z)
        {
            if (x < 0 || x >= sizeX) return true;
            if (z < 0 || z >= sizeZ) return true;
            return false;
        }

        private bool IsChecked(Node n)
        {
            if (MatrixEntryPoint[n.x, n.z] == null) 
                return false;
            return true;
        }

        /// <summary>
        /// Setto come visitato il nodo
        /// Se questo è il target, lo segno anche come nodo visitato
        /// </summary>
        /// <param name="n"></param>
        private void SetChecked(Node n)
        {
            MatrixEntryPoint[n.x, n.z] = n;

            if( n.Value == target)
                ListVisited.Add(n);
        }

        private Node SetValue(Node node)
        {
            if ( IsOutOfBounds(node.x , node.z) )
            {
                node.Value = -1;
                return node;
            }
            else
            {
                node.Value = biomeMap[node.x, node.z];
                return node;
            }
        }


        public int rowCount {
            get {
                return this.sizeX;
            }
        }

        public int colCount {
            get {
                return this.sizeZ;
            }
        }

        public Node GetRandomNodeVisited()
        {
            int i = UnityEngine.Random.Range(0, ListVisited.Count - 1);
            return ListVisited[i];
        }

    }
}



//private void PreProcessing(float[,] _matrixToScan, float waterLevel)
//{
//    matrix_discrete = new int[_matrixToScan.GetLength(0),
//                            _matrixToScan.GetLength(1)];

//    matrix_entry_point = new Node[_matrixToScan.GetLength(0),
//                                  _matrixToScan.GetLength(1)];

//    for (int r = 0; r < matrix_discrete.GetLength(0); r++)
//    {
//        for (int c = 0; c < matrix_discrete.GetLength(1); c++)
//        {
//            if (_matrixToScan[r, c] < waterLevel)
//                matrix_discrete[r, c] = 0;
//            else
//                matrix_discrete[r, c] = 1;
//        }
//    }
//}